package org.fastsyncer.plugin.impl;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.fastsyncer.common.DefaultThreadFactory;
import org.fastsyncer.common.constant.ThirdpartyConstant;
import org.fastsyncer.common.entity.DatabaseConfig;
import org.fastsyncer.common.util.ApplicationUtil;
import org.fastsyncer.connector.mysql.MysqlConnector;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * <pre>
 * 定时同步人员、岗位的增量数据时间点可能一致,为了防止岗位先于人员同步,导致出现业务逻辑问题。
 * 这里增加补救措施：
 * 1、间隔30s重新同步有误数据（sd_ma_account或sd_user无法找到该帐号）
 * 2、总共有3次尝试补救机会
 * </pre>
 * @author AE86
 * @date 2018年10月25日 下午4:27:43
 * @version 1.0.0
 */
public abstract class AbstractHKPositionConvert {

    private final Logger logger = LoggerFactory.getLogger(AbstractHKPositionConvert.class);
    
    /**
     * 根据用户ID查询企业帐号ID
     * <p>
     * SELECT A.ID FROM SD_MA_ACCOUNT A WHERE A.USER_ID = ? AND A.IS_DELETED = '0'
     * </p>
     */
    protected static final String QUERY_ACCOUNT_BY_USER_ID = "SELECT A.ID FROM SD_MA_ACCOUNT A WHERE A.USER_ID = ? AND A.IS_DELETED = '0'";

    /**
     * 根据ID查询企业组织名称
     * <p>
     * SELECT BASEORG_NAME FROM SD_BASEORG WHERE ID = ? AND IS_DELETED = '0'
     * </p>
     */
    protected static final String QUERY_ORG_BY_ID = "SELECT BASEORG_NAME FROM SD_BASEORG WHERE ID = ? AND IS_DELETED = '0'";

    /**
     * 根据企业帐号ID查询关联的应用帐号
     * <pre>
     * SELECT
     *     I.ID,
     *     I.APP_ID,
     *     I.MI_ACCOUNT,
     *     I.APPORG_ID AS OLD_APPORG_ID,
     *     (SELECT ID FROM SD_APPORG O WHERE O.APP_ID = I.APP_ID AND O.SYNC_BASEORG_ID = ? AND O.IS_DELETED = '0') 
     *     AS APPORG_ID
     * FROM
     *     SD_MI_ACCOUNT I
     * INNER JOIN SD_MA_MI_REL R ON (R.MI_ID = I.ID)
     * INNER JOIN SD_MA_ACCOUNT A ON (A.ID = R.MA_ID)
     * WHERE
     *     A.IS_DELETED = '0'
     * AND R.IS_DELETED = '0'
     * AND I.IS_DELETED = '0'
     * AND I.APPACCOUNT_TYPE = '1'
     * AND A.ID = ?
     * </pre>
     */
    protected static final String QUERY_MI_ACCOUNT_BY_MA_ID = "SELECT I.ID,I.APP_ID,I.MI_ACCOUNT,I.APPORG_ID AS OLD_APPORG_ID,(SELECT ID FROM SD_APPORG O WHERE O.APP_ID = I.APP_ID AND O.SYNC_BASEORG_ID = ? AND O.IS_DELETED = '0') AS APPORG_ID FROM SD_MI_ACCOUNT I INNER JOIN SD_MA_MI_REL R ON (R.MI_ID = I.ID) INNER JOIN SD_MA_ACCOUNT A ON (A.ID = R.MA_ID) WHERE A.IS_DELETED = '0' AND R.IS_DELETED = '0' AND I.IS_DELETED = '0' AND I.APPACCOUNT_TYPE = '1' AND A.ID = ?";
    
    /**
     * 根据应用帐号ID修改应用帐号状态,应用组织,更新时间
     * <pre>
     * UPDATE SD_MI_ACCOUNT I
     *     SET I.APPORG_ID = ?, I.APPACCOUNT_STATUS = ?, I.LAST_DATE = ?
     * WHERE
     *     I.ID = ?
     * </pre>
     */
    protected static final String UPDATE_MI_ACCOUNT_BY_ID = "UPDATE SD_MI_ACCOUNT I SET I.APPORG_ID = ?, I.APPACCOUNT_STATUS = ?, I.LAST_DATE = ? WHERE I.ID = ?";

    /**
     * 根据企业帐号ID修改企业组织
     * <p>
     * UPDATE SD_MA_ACCOUNT SET BASEORG_ID = ?, ACCOUNT_STATUS = ? WHERE ID = ? AND IS_DELETED = '0'
     * </p>
     */
    protected static final String UPDATE_ACCOUNT_BY_ID = "UPDATE SD_MA_ACCOUNT SET BASEORG_ID = ?, ACCOUNT_STATUS = ? WHERE ID = ? AND IS_DELETED = '0'";

    /**
     * 根据用户ID修改人员信息
     * <p>
     * UPDATE SD_USER SET BASEORG_ID = ?, BASEORG_NAME = ?, EMPL_CLASS = ?, USER_STATUS = ?, CTS_EMPL_ATTRI = ?, CTS_EMPL_ATTRIDESC = ?, CTS_ENTRY_DT = ?, CTS_TOFORMAL_DT = ?, TERMINATION_DT = ? WHERE ID = ? AND IS_DELETED = '0'
     * </p>
     */
    protected static final String UPDATE_USER_BY_ID = "UPDATE SD_USER SET BASEORG_ID = ?, BASEORG_NAME = ?, EMPL_CLASS = ?, USER_STATUS = ?, CTS_EMPL_ATTRI = ?, CTS_EMPL_ATTRIDESC = ?, CTS_ENTRY_DT = ?, CTS_TOFORMAL_DT = ?, TERMINATION_DT = ? WHERE ID = ? AND IS_DELETED = '0'";

    /**
     * 根据ID修改帐号与岗位关系
     * <p>
     * UPDATE SD_POST_MA_REL SET MA_ID = ? WHERE ID = ?
     * </p>
     */
    private static final String UPDATE_POS_BY_ID = "UPDATE SD_POST_MA_REL SET MA_ID = ? WHERE ID = ?";

    /**
     * http://10.238.206.223:18200/asdview/coremail/rest/coremail/updateCoremailAccount
     */
    private final String URL = ApplicationUtil.getKey(ThirdpartyConstant.THIRDPARTY_AOSTAR_APPORG_REQUEST_URL) + "/updateCoremailAccount";
    
    private static final Queue<MSG> QUEUE = new LinkedBlockingQueue<MSG>();

    private static final int MAX_CHANCE = 3;

    private static final int PERIOD = 30;

    // 线程池回收时间（毫秒）,如果在2分钟之内缓存数据仍然为空则回收线程池
    private static final long EXPIRED_THREAD_POOL_TIME = 120 * 1000;

    private DatabaseConfig config;

    private ScheduledExecutorService pool;

    private JdbcTemplate jdbc;

    private long expiredTime;

    /**
     * 发送HTTP POST请求
     * @param args
     */
    public void post(JSONArray array) {
        List<NameValuePair> accList = new ArrayList<>(1);
        accList.add(new BasicNameValuePair("json", array.toString()));
        post(accList);
    }
    
    private void post(List<NameValuePair> args) {
        CloseableHttpClient client = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        try {
            // URL
            HttpPost post = new HttpPost(URL);
            if(logger.isDebugEnabled()){
                logger.debug(URL);
                logger.debug(args.toString());
            }
            UrlEncodedFormEntity arg = new UrlEncodedFormEntity(args, "UTF-8");
            post.setEntity(arg);
            // 发送请求
            response = client.execute(post);
            HttpEntity entity = response.getEntity();
            String msg = EntityUtils.toString(entity);
            logger.info(msg);
        } catch (ClientProtocolException e) {
            logger.error(e.getLocalizedMessage());
        } catch (IOException e) {
            logger.error(e.getLocalizedMessage());
        } finally {
            try {
                if (null != response) {
                    response.close();
                }
                if (null != client) {
                    client.close();
                }
            } catch (IOException e) {
                logger.error(e.getLocalizedMessage());
            }
        }
    }
    
    /**
     * 记录无效数据
     * @param config 目标源DatabaseConfig
     * @param emplRcd
     * @param baseorgId
     * @param emplId
     * @param emplClass
     * @param emplStatus
     * @param emplAttri 员工属性
     * @param emplAttriDesc 员工属性描述
     * @param ctsEntryDt
     * @param ctsToformalDt
     * @param terminationDt
     */
    public void offer(DatabaseConfig config, String emplRcd, String baseorgId, String emplId, String emplClass, String emplStatus, String emplAttri, String emplAttriDesc,
            Timestamp ctsEntryDt, Timestamp ctsToformalDt, Timestamp terminationDt) {
        if (null != config) {
            // 将同步的错误数据扔队列
            MSG msg = new MSG(emplRcd, baseorgId, emplId, emplClass, emplStatus, emplAttri, emplAttriDesc, ctsEntryDt, ctsToformalDt, terminationDt).setVersion(MAX_CHANCE);
            QUEUE.offer(msg);
            this.config = config;
            // 定时执行同步
            setupConfig();
        }
    }

    private synchronized void release() {
        if (null != pool) {
            pool.shutdownNow();
        }
        if (null != jdbc) {
            MysqlConnector.getInstance().close(jdbc);
        }
        pool = null;
        jdbc = null;
    }

    // 这里使用的单线程执行,run不存在并发
    private void run() {
        Queue<MSG> tem = null;
        while (!QUEUE.isEmpty()) {
            try {
                MSG m = QUEUE.poll();
                int v = m.getVersion() - 1;
                // 如果更新失败 && 还有尝试机会
                if (!update(m) && v > 0) {
                    if (null == tem) {
                        tem = new LinkedBlockingQueue<MSG>();
                    }
                    tem.offer(m.setVersion(v));
                }
            } catch (Exception ex) {
                continue;
            }
        }
        // 放入失败任务到队列,等待下一次继续执行
        if (null != tem) {
            QUEUE.addAll(tem);
        }
        // 没有任务 && 超过线程池创建有效期
        if (QUEUE.isEmpty() && System.currentTimeMillis() > expiredTime) {
            // 释放连接资源
            release();
            // 可能在停止线程的一刻,正好有新的任务
            if (!QUEUE.isEmpty()) {
                setupConfig();
            }
        }

    }

    private void setupConfig() {
        // 标记线程池回收时间
        expiredTime = System.currentTimeMillis() + EXPIRED_THREAD_POOL_TIME;
        if (null == jdbc || null == pool) {
            // 确保已释放连接
            release();
            jdbc = MysqlConnector.getInstance().getJdbcTemplate(this.config);
            pool = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("AbstractHKPosConvert-threadPool"));
            // 延迟2秒后以间隔30s的频率执行run方法
            pool.scheduleAtFixedRate(() -> {
                run();
            }, 2, PERIOD, TimeUnit.SECONDS);
        }
    }

    private boolean update(MSG m) {
        if (null == jdbc) {
            throw new NullPointerException("The jdbc is null.");
        }
        final String emplRcd = m.getEmplRcd();
        final String baseorgId = m.getBaseorgId();
        final String emplId = m.getEmplId();
        final String emplClass = m.getEmplClass();
        final String emplStatus = m.getEmplStatus();
        final String emplAttri = m.getEmplAttri();
        final String emplAttriDesc = m.getEmplAttriDesc();
        final Timestamp ctsEntryDt = m.getCtsEntryDt();
        final Timestamp ctsToformalDt = m.getCtsToformalDt();
        final Timestamp terminationDt = m.getTerminationDt();

        // 1.根据人员ID查询sd_ma_account表中的ID
        // 获取企业帐号ID
        String maId = null;
        Map<String, Object> row = null;
        try {
            // SELECT A.ID FROM SD_MA_ACCOUNT A LEFT JOIN SD_BASEORG O ON(A.BASEORG_ID=O.ID) WHERE A.USER_ID = ? AND A.IS_DELETED = '0' 
            row = jdbc.queryForMap(QUERY_ACCOUNT_BY_USER_ID, new Object[] { emplId });
            maId = (String) row.get("ID");
        } catch (DataAccessException e) {
            row = null;
        }
        if (StringUtils.isBlank(maId)) {
            return false;
        }

        Object[] args = null;
        int update;
        // 处理主岗类型数据
        if ("0".equals(emplRcd)) {
            String userStatus = null;
            String accountStatus = null;
            String accountMiStatus = null;
            // 2.处理员工状态 A为在职 I为离职
            if ("A".equalsIgnoreCase(emplStatus)) {
                // 现职
                userStatus = "1";
                // 禁用
                accountStatus = "1";
                // 启用
                accountMiStatus = "1";
            } else if ("I".equalsIgnoreCase(emplStatus)) {
                // 离职
                userStatus = "5";
                // 禁用
                accountStatus = "2";
                // 禁用
                accountMiStatus = "0";
            }
            // 3.根据企业组织ID查询sd_baseorg表中的企业组织名称
            String baseorgName = null;
            try {
                // SELECT BASEORG_NAME FROM SD_BASEORG WHERE ID = ? AND IS_DELETED = '0'
                row = jdbc.queryForMap(QUERY_ORG_BY_ID, new Object[] { baseorgId });
                baseorgName = (String) row.get("BASEORG_NAME");
            } catch (DataAccessException e) {
                row = null;
            }
            // 4.根据人员ID修改sd_user表中的企业组织ID,企业组织名称,员工类型,员工状态,入职日期,转正日期,离职日期
            // UPDATE SD_USER SET BASEORG_ID = ?, BASEORG_NAME = ?, EMPL_CLASS = ?, USER_STATUS = ?, CTS_EMPL_ATTRID = ?, CTS_EMPL_ATTRIDESC = ?, CTS_ENTRY_DT = ?, CTS_TOFORMAL_DT = ?, TERMINATION_DT = ? WHERE ID = ? AND IS_DELETED = '0'
            args = new Object[] { baseorgId, baseorgName, emplClass, userStatus, emplAttri, emplAttriDesc, ctsEntryDt, ctsToformalDt, terminationDt, emplId };
            update = jdbc.update(UPDATE_USER_BY_ID, args);
            if (0 == update) {
                return false;
            }
            // 5.更新企业帐号企业组织ID
            // UPDATE SD_MA_ACCOUNT SET BASEORG_ID = ?, ACCOUNT_STATUS = ? WHERE ID = ? AND IS_DELETED = '0' 
            args = new Object[] { baseorgId, accountStatus, maId };
            update = jdbc.update(UPDATE_ACCOUNT_BY_ID, args);
            // 6.更新企业帐号关联的应用帐号状态
            args = new Object[] { baseorgId, maId };
            List<Map<String, Object>> list = jdbc.queryForList(QUERY_MI_ACCOUNT_BY_MA_ID, args);
            // 7.更新应用帐号状态,应用组织,更新时间
            if (null != list && !list.isEmpty()) {
                JSONArray array = new JSONArray();
                JSONObject obj = null;
                for (Map<String, Object> r : list) {
                    Object newAppOrgId = r.get("APPORG_ID");
                    Object miId = r.get("ID");
                    args = new Object[] { newAppOrgId, accountMiStatus, new Timestamp(System.currentTimeMillis()), miId };
                    jdbc.update(UPDATE_MI_ACCOUNT_BY_ID, args);

                    obj = new JSONObject();
                    row.put("MA_ID", maId);
                    row.put("MI_ID", miId);
                    row.put("APP_ID", (String) r.get("APP_ID"));
                    row.put("MI_ACCOUNT", (String) r.get("MI_ACCOUNT"));
                    row.put("OLD_APPORG_ID", (String) r.get("OLD_APPORG_ID"));
                    row.put("NEW_APPORG_ID", (String) newAppOrgId);
                    array.put(obj);
                }
                // 7.1 将更新的应用帐号通过Http推送给4A
                post(array);
            }
        }

        // 7.更新帐号与岗位关系表
        // UPDATE SD_POST_MA_REL SET MA_ID = ? WHERE ID = ? 
        String id = emplId.concat(emplRcd);
        args = new Object[] { maId, id };
        update = jdbc.update(UPDATE_POS_BY_ID, args);
        return true;
    }

}

class MSG {
    int version;

    String emplRcd;

    String baseorgId;

    String emplId;

    String emplClass;

    String emplStatus;

    String emplAttri;

    String emplAttriDesc;

    Timestamp ctsEntryDt;

    Timestamp ctsToformalDt;

    Timestamp terminationDt;

    public MSG(String emplRcd, String baseorgId, String emplId, String emplClass, String emplStatus, String emplAttri, String emplAttriDesc, Timestamp ctsEntryDt,
            Timestamp ctsToformalDt, Timestamp terminationDt) {
        super();
        this.emplRcd = emplRcd;
        this.baseorgId = baseorgId;
        this.emplId = emplId;
        this.emplClass = emplClass;
        this.emplStatus = emplStatus;
        this.emplAttri = emplAttri;
        this.emplAttriDesc = emplAttriDesc;
        this.ctsEntryDt = ctsEntryDt;
        this.ctsToformalDt = ctsToformalDt;
        this.terminationDt = terminationDt;
    }

    public int getVersion() {
        return version;
    }

    public MSG setVersion(int version) {
        this.version = version;
        return this;
    }

    public String getEmplRcd() {
        return emplRcd;
    }

    public String getBaseorgId() {
        return baseorgId;
    }

    public String getEmplId() {
        return emplId;
    }

    public String getEmplClass() {
        return emplClass;
    }

    public String getEmplStatus() {
        return emplStatus;
    }

    public String getEmplAttri() {
        return emplAttri;
    }

    public String getEmplAttriDesc() {
        return emplAttriDesc;
    }

    public Timestamp getCtsEntryDt() {
        return ctsEntryDt;
    }

    public Timestamp getCtsToformalDt() {
        return ctsToformalDt;
    }

    public Timestamp getTerminationDt() {
        return terminationDt;
    }

}
